/*
Copyright (C) 2012-2013 Carlo de Falco

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 3 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with Octave; see the file COPYING.  If not, see
  <http://www.gnu.org/licenses/>.  

Author: Carlo de Falco <carlo@guglielmo.local>
Created: 2012-11-22

*/

#include <octave_file_io_utils.h>
#include <octave_file_io.h>

//---------------------------------------------------------------------
//                Methods in the singleton class
//---------------------------------------------------------------------

int
octave_file_io_intf::fopen (const char *fname, std::ios::openmode m)
{

  // std::cout << "\tfopen" << std::endl;
  // std::cout << "\tmode = " << m << std::endl;

  if (file.is_open ())
    file.close ();
  
  filename = fname;
  file.open (fname, m);
  
  if (m & std::ios::in)
    {
      current_mode = 0;
      if (read_binary_file_header (file, swap, flt_fmt, true) != 0)
	return -1;
    }
  else if (m & std::ios::out)
    {
      write_header (gzofile, format);
      current_mode = 2;
    }
  // std::cout << "\tcurrent_mode = " << current_mode << std::endl;

  return 0;
}

int 
octave_file_io_intf::fclose (void)
{

  if (file.is_open ())
    file.close ();

  filename = "";
  return 0;
}

int
octave_file_io_intf::gzfopen (const char *fname, std::ios::openmode m)
{

  
  // std::cout << "\tgzfopen" << std::endl;
  // std::cout << "\tm = " << m << std::endl;

  if (file.is_open ())
    file.close ();
   
  if (m & std::ios::in)
    {
      gzifile.open (fname, m);
      filename = fname;
      current_mode = 1;
      if (read_binary_file_header (gzifile, swap, flt_fmt, true) != 0)
	return -1;
    }
  else if (m & std::ios::out)
    {
      gzofile.open (fname, m);
      filename = fname;
      write_header (gzofile, format);
      current_mode = 3;
    }
  else
    return -1;

  // std::cout << "\tcurrent_mode = " << current_mode << std::endl;
  return 0;
}

int 
octave_file_io_intf::gzfclose (void)
{

  if (gzifile.is_open ())
    {
      gzifile.close ();
      filename = "";
    }

  if (gzofile.is_open ())
    {
      gzofile.close ();
      filename = "";
    }

  return 0;
}


int
octave_file_io_intf::read
(const std::string &varname)
{
  string_vector argv (1);
  install_types ();  
  argv(0) = varname;
  
  file.clear ();
  file.seekg (0);
  if (read_binary_file_header (gzifile, swap, flt_fmt, true) != 0)
    return -1;

  octave_scalar_map m = do_load (file, filename.c_str (), format, 
				 flt_fmt, false, swap, true, argv, 
				 0, 1, 1).scalar_map_value ();
  
  buffer =  m.contents (varname).array_value ();
  if (error_state)
    return -1;
  else
    return 0;
}

int
octave_file_io_intf::gzread 
(const std::string &varname)
{
  string_vector argv (1);
  install_types ();  
  argv(0) = varname;
 
  // std::cout << "\tgzread" << std::endl;
  // std::cout << "\tvarname = " << varname << std::endl;
  // std::cout << "\tfilename = " << filename << std::endl;
  
  gzifile.clear ();
  gzifile.seekg (0);
  if (read_binary_file_header (gzifile, swap, flt_fmt, true) != 0)
    return -1;

  octave_scalar_map m = do_load (gzifile, filename.c_str (), format, 
				 flt_fmt, false, swap, true, argv,
				 0, 1, 1).scalar_map_value ();
  
  buffer =  m.contents (varname).array_value ();
  // std::cout << "\tc = " << c << std::endl;

  if (error_state)
    return -1;
  else
    return 0;  
}

int
octave_file_io_intf::do_read 
(const std::string &varname)
{
  // std::cout << "\tdo_read" << std::endl;
  // std::cout << "\tcurrent_mode = " << current_mode << std::endl;
  switch (current_mode)
    {
    case 2:
    case 3:
      return -1;
    case 0:
      return read (varname);
      break;
    case 1:
      return gzread (varname);
      break;
    default:
      return -1;
    }
}

int
octave_file_io_intf::do_write
(const std::string &varname)
{
  switch (current_mode)
    {
    case 0:
    case 1:
      return -1;
    case 2:
      return write (varname);
      break;
    case 3:
      return gzwrite (varname);
      break;
    default:
      return -1;
    }
}

int
octave_file_io_intf::write 
(const std::string &varname)
{
  if (! save_binary_data (file, buffer, varname, "", false, false))
    return -1;
  return 0;
}

int
octave_file_io_intf::gzwrite
(const std::string &varname)
{
  if (! save_binary_data (gzofile, buffer, varname, "", false, false))
    return -1;
  return 0;  
}

void 
octave_file_io_intf::get_data_shape (int* ishape) 
{
  dim_vector d = buffer.dims ();
  for (int ii = 0; ii < get_data_rank (); ++ii)
    *(ishape + ii) = d(ii);
}

void 
octave_file_io_intf::set_data_shape (const int rank, const int* ishape) 
{
  dim_vector d;
  if (rank > 1)
    d.resize (rank);
  else
    {d.resize (2); d(0) = 1; d(1) = 1;}
  
  for (int ii = 0; ii < rank; ++ii)
    d(ii) = *(ishape + ii);	
  
  buffer.resize (d);
}



//---------------------------------------------------------------------
//       Functions with C linking for the F2003 interface
//---------------------------------------------------------------------

static octave_file_io_intf io_intf;

int
octave_io_open (const char* fname, int *mode_in, int *mode_out)
{

  *mode_out = *mode_in;
  
  std::ios::openmode imode = std::ios::in | std::ios::binary;
  std::ios::openmode omode = std::ios::out | std::ios::binary;
  
  // std::cout << "\toctave_io_open" << std::endl;

  bool gziped = false;
  if (fexists (std::string (fname)))
    gziped = check_gzip_magic (fname);

  // std::cout << "\tgziped = " << gziped << std::endl;

  // std::cout << "\ton input: *mode_in = " << *mode_in << std::endl;
  // std::cout << "\ton input: *mode_out = " << *mode_out << std::endl;
  int retval = 0;
  if (gziped && (*mode_out == 0 || *mode_out == 2))
    { ++(*mode_out); retval = 1; }

  // std::cout << "\ton output: *mode = " << *mode_out << std::endl;

  switch (*mode_out)
    {
    case 0: // read uncompressed
      if (io_intf.fopen (fname, imode) != 0)
	retval = -1;
      break;
    case 1: // read compressed
      if (io_intf.gzfopen (fname, imode) != 0)
	retval = -1;
      break;
    case 2: // write uncompressed
      if (io_intf.fopen (fname, omode) != 0)
	retval = -1;
      break;
    case 3: // write compressed
      if (io_intf.gzfopen (fname, omode) != 0)
	retval = -1;
      break;
    default:
      retval = -1;
    }

  // std::cout << "\ton output: *mode_out = " << *mode_out << std::endl;
  // std::cout << "\ton output: retval = " << retval << std::endl;
  return (retval);
}

int
octave_io_close (void)
{
  io_intf.fclose ();
  io_intf.gzfclose ();
  return 0;
}

int
octave_load (const char* varname, double** data, 
	     int* rank, int* shape)
{
  int res = 0;

  //std::cout << "do_read" << std::endl;
  res = io_intf.do_read (varname);

  //std::cout << "get_data" << std::endl;
  *data = io_intf.get_data ();

  //std::cout << "get_data_rank" << std::endl;
  *rank = io_intf.get_data_rank ();
  
  //std::cout << "get_data_shape" << std::endl;
  io_intf.get_data_shape (shape);
  
  return res;
}

int
octave_save (const char* varname, const double* data, 
	     const int* rank, const int* shape)
{
  int res = 0;
  //std::cout << *shape << std::endl;
  //for (int ii=0; ii<*shape; ++ii)
  //  std::cout << *(data+ii) << std::endl;
  io_intf.set_data_shape (*rank, shape);
  
  memcpy (io_intf.get_data (), data, 
	  io_intf.get_data_size () * sizeof (double));
  
  res = io_intf.do_write (varname);
  
  io_intf.clear_data ();

  return res;
}

int
octave_clear (void)
{
  int res = 0;
  io_intf.clear_data ();
  return res;
}


//---------------------------------------------------------------------
//                    Static utility functions
//---------------------------------------------------------------------

static bool
check_gzip_magic (const std::string& fname)
{
  bool retval = false;
  std::ifstream file (fname.c_str ());
  OCTAVE_LOCAL_BUFFER (unsigned char, magic, 2);

  if (file.read (reinterpret_cast<char *> (magic), 2) 
      && magic[0] == 0x1f 
      && magic[1] == 0x8b)

    retval = true;

  file.close ();
  return retval;
}


static bool 
fexists (const std::string& fname)
{
  std::ifstream ifile (fname.c_str ());
  return ifile;
}
